﻿using System;

namespace ConsoleApplication1
{
    public delegate Tuple<T, Stream<T>> Stream<T>();

    static class ChainExtensions
    {
        public static Stream<TResult> Chain<T, TResult>(this T it, Func<T, Tuple<TResult, T>> func)
        {
            return () => {
                var tuple = func(it);
                return Tuple.Create(tuple.Item1, Chain(tuple.Item2, func));
            };
        }
    }

    class Program
    {
        static void Main()
        {
            Console.WriteLine(EstimatePi(1000));
        }

        static double EstimatePi(int trials)
        {
            return Math.Sqrt(6/MonteCarlo(trials, 0.Chain(x => Tuple.Create(x, RandUpdate(x)))
                .Chain(random => {
                    var tuple2 = random();
                    var tuple3 = tuple2.Item2();
                    return Tuple.Create(Gcd(tuple2.Item1, tuple3.Item1) == 1, tuple3.Item2);
                })));
        }

        static double MonteCarlo(int trials, Stream<bool> experiment)
        {
            return MonteCarloIter(trials, trials, 0, experiment);
        }

        static double MonteCarloIter(int trials, int trialsRemaining, int trialsPassed,
            Stream<bool> experiment)
        {
            if (trialsRemaining == 0) return (double) trialsPassed/trials;
            var tuple = experiment();
            var passed = tuple.Item1 ? trialsPassed + 1 : trialsPassed;
            return MonteCarloIter(trials, trialsRemaining - 1, passed, tuple.Item2);
        }

        private static int RandUpdate(int x)
        {
            const int a = 27;
            const int b = 26;
            const int m = 127;
            return (a*x + b)%m;
        }

        static int Gcd(int a, int b)
        {
            return b == 0 ? a : Gcd(b, a % b);
        }
    }
}